<?php

declare( strict_types = 1 );

namespace App\Common\Model\Base\DataUnit\DataOperates;

use App\Common as com;

class FieldStruct extends com\Cla\ClaBase {

	public static int $varType_String = 1;

	public static int $varType_JsonArray = 2;

	public static int $varType_JsonArrayAppend = 3;

	public static int $varType_StringArray = 4;

	public static int $varType_StringArrayAppend = 5;

	/**
	 * 格式：
	 *    [
	 *        '{fieldName}' => [
	 *            'value' => '1',
	 *            'default' => '2',
	 *            'issetValue' => false,
	 *        ],
	 *        ...
	 *    ]
	 * @var array
	 */
	private array $fieldStructDatas;

	/**
	 * $fieldStructDatas最后一次被重置的数据
	 * 格式：
	 *    [
	 *        '{fieldName}' => [
	 *            'value' => '1',
	 *            'default' => '2',
	 *            'issetValue' => false,
	 *        ],
	 *        ...
	 *    ]
	 * @var array
	 */
	private array $lastResetFieldStructDatas;

	/**
	 * FieldStruct constructor.
	 * @param array $fieldData
	 */
	public function __construct( array $fieldData ) {
		foreach( $fieldData as $fieldKey => $fieldValue ){
			$this->fieldStructDatas[ $fieldKey ] = $this->buildFieldStructData( $fieldValue );
		}
		//
		parent::__construct();
	}

	/**
	 * @param mixed $fieldValue
	 * @return array
	 */
	private function buildFieldStructData( mixed $fieldValue ):array {
		return [
			'type' => self::$varType_String,
			// [1]值 [2]array-json
			'value' => $fieldValue,
			'default' => $fieldValue,
			'issetValue' => false,
			'lock' => false,
			// [bool] 锁状态
		];
	}

	/**
	 * 如果数据不存在则抛出异常
	 * @param string $fieldName
	 * @return $this
	 * @throws
	 */
	private function existFieldOrFail( string $fieldName ):self {
		if( empty( $this->fieldStructDatas[ $fieldName ] ) ){
			throw com\Lib\Err\Exception::makeOfNew( "fieldStruct 数据不存在({$fieldName})", com\Lib\Err\Code::$sysDataNotExist );
		}
		//
		return $this;
	}

	/**
	 * 是否已上锁
	 * @param string $fieldName
	 * @return bool
	 * @throws
	 */
	public function isLock( string $fieldName ):bool {
		$this->existFieldOrFail( $fieldName );
		//
		return $this->fieldStructDatas[ $fieldName ]['lock'];
	}

	/**
	 * 如果数据已锁则抛出异常
	 * @param string $fieldName
	 * @return $this
	 * @throws
	 */
	private function isLockFail( string $fieldName ):self {
		if( $this->isLock( $fieldName ) ){
			throw com\Lib\Err\Exception::makeOfNew( '数据已被锁定，修改失败', com\Lib\Err\Code::$sysLockLose );
		}
		//
		return $this;
	}

	/**
	 * 上锁
	 * @param string $fieldName
	 * @return $this
	 */
	public function lock( string $fieldName ):self {
		$this->fieldStructDatas[ $fieldName ]['lock'] = true;
		//
		return $this;
	}

	/**
	 * 解锁
	 * @param string $fieldName
	 * @return $this
	 */
	public function unlock( string $fieldName ):self {
		$this->fieldStructDatas[ $fieldName ]['lock'] = false;
		//
		return $this;
	}

	/**
	 * 判断是否存在
	 * @param string $fieldName
	 * @return bool
	 */
	public function isset( string $fieldName ):bool {
		return isset( $this->fieldStructDatas[ $fieldName ]['value'] );
	}

	/**
	 * 判断是否存在已修改的值
	 * @param string $fieldName
	 * @return bool
	 */
	public function issetNewValue( string $fieldName ):bool {
		return $this->fieldStructDatas[ $fieldName ]['issetValue'];
	}

	/**
	 * 修改值
	 * @param string $fieldName
	 * @param mixed $value
	 * @return $this
	 */
	public function setValue( string $fieldName, mixed $value ):self {
		$this->isLockFail( $fieldName );
		//
		$this->fieldStructDatas[ $fieldName ]['issetValue'] = true;
		//
		if( $this->fieldStructDatas[ $fieldName ]['type'] !== self::$varType_String ){
			$this->fieldStructDatas[ $fieldName ]['type'] = self::$varType_String;
		}
		$this->fieldStructDatas[ $fieldName ]['value'] = $value;
		//
		return $this;
	}

	/**
	 * 设置默认值
	 * @param string $fieldName
	 * @param mixed|\Closure $defaultValue 数据(支持传入数据闭关)
	 * @return $this
	 */
	public function setDefaultValue( string $fieldName, mixed $defaultValue ):self {
		$this->fieldStructDatas[ $fieldName ]['default'] = $defaultValue;
		//
		return $this;
	}

	/**
	 * 修改数组（完整覆盖）
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfJsonArray( string $fieldName, string $keyPath, mixed $value ):self {
		return $this->setValueOfArray( self::$varType_JsonArray, $fieldName, $keyPath, $value );
	}

	/**
	 * 修改数组（追加）
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfJsonArrayAppend( string $fieldName, string $keyPath, mixed $value ):self {
		return $this->setValueOfArrayAppend( self::$varType_JsonArrayAppend, $fieldName, $keyPath, $value );
	}

	/**
	 * 修改数组（完整覆盖）
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfStringArray( string $fieldName, string $keyPath, mixed $value ):self {
		return $this->setValueOfArrayAppend( self::$varType_StringArray, $fieldName, $keyPath, $value );
	}

	/**
	 * 修改数组（追加）
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfStringArrayAppend( string $fieldName, string $keyPath, mixed $value ):self {
		return $this->setValueOfArrayAppend( self::$varType_StringArrayAppend, $fieldName, $keyPath, $value );
	}

	/**
	 * 修改数组（完整覆盖）
	 * @param int $type
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfArray( int $type, string $fieldName, string $keyPath, mixed $value ):self {
		$this->isLockFail( $fieldName );
		//
		$this->fieldStructDatas[ $fieldName ]['issetValue'] = true;
		//
		if( $this->fieldStructDatas[ $fieldName ]['type'] !== $type ){
			$this->fieldStructDatas[ $fieldName ]['type'] = $type;
		}
		$this->fieldStructDatas[ $fieldName ]['value'][ $keyPath ] = $value;
		//
		return $this;
	}

	/**
	 * 修改数组（追加）
	 * @param int $type
	 * @param string $fieldName
	 * @param string $keyPath
	 * @param mixed $value
	 * @return $this
	 */
	public function setValueOfArrayAppend( int $type, string $fieldName, string $keyPath, mixed $value ):self {
		$this->isLockFail( $fieldName );
		//
		$this->fieldStructDatas[ $fieldName ]['issetValue'] = true;
		//
		$fieldDataValueQuote = &$this->fieldStructDatas[ $fieldName ]['value'];
		if( $keyPath !== '' ){
			$fieldDataValueQuote = &$fieldDataValueQuote[ $keyPath ];
		}
		//
		if( $this->fieldStructDatas[ $fieldName ]['type'] !== $type ){
			$this->fieldStructDatas[ $fieldName ]['type'] = $type;
			$fieldDataValueQuote = [];

		}
		if( empty( $fieldDataValueQuote ) ){
			$fieldDataValueQuote = $value;
		} else{
			// 覆盖/增加数据
			$fieldDataValueQuote = com\Lib\Fun\Array_::arrayMerges( $fieldDataValueQuote, $value );
		}
		//
		return $this;
	}

	/**
	 * 获取数据类型
	 * @param string $fieldName
	 * @return int|null [int]参考：self::$varType_Xxx [null]不存在
	 */
	public function getSetType( string $fieldName ):?int {
		return $this->fieldStructDatas[ $fieldName ]['type'] ?? null;
	}

	/**
	 * 获取原始数据(且内置默认值无效)
	 * @param string $fieldName
	 * @param mixed $default
	 * @return mixed
	 */
	public function getOriginalValue( string $fieldName, mixed $default = null ) {
		return $this->fieldStructDatas[ $fieldName ]['value'] ?? $default;
	}

	/**
	 * 获取
	 * @param string $fieldName
	 * @param mixed $default
	 * @return mixed
	 */
	public function getValue( string $fieldName, mixed $default = null ) {
		if( $this->isset( $fieldName ) ){
			return $this->extractValue( $this->fieldStructDatas[ $fieldName ]['value'] );
		} else{
			return $this->getDefaultValue( $fieldName, $default );
		}
	}

	/**
	 * 获取(从重置数据中获取)
	 * @param string $fieldName
	 * @return mixed
	 * @throws
	 */
	public function getValueAsResetOrFail( string $fieldName ):mixed {
		if( array_key_exists( $fieldName, $this->lastResetFieldStructDatas ) ){
			return $this->lastResetFieldStructDatas[ $fieldName ]['value'];
		} else{
			throw com\Lib\Err\Exception::makeOfNew( 'Reset数据不存在', com\Lib\Err\Code::$sysDataNotExist );
		}
	}

	/**
	 * 获取
	 * @param string $fieldName
	 * @param mixed $default
	 * @return mixed
	 */
	public function getValueAsSqlData( string $fieldName, mixed $default = null ) {
		$data = $this->getValue( $fieldName, $default );
		if( is_array( $data ) ){
			return json_encode( $data );
		}
		//
		return $data;
	}

	/**
	 * 获取，如果不存在则抛出异常
	 * @param string $fieldName
	 * @return mixed
	 */
	public function getValueOrFail( string $fieldName ) {
		if( ! $this->isset( $fieldName ) ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}
		return $this->getValue( $fieldName );
	}

	/**
	 * 获取默认值(预设默认值)
	 * @param string $fieldName
	 * @param null $default 注意：如果记录不存在，则优先返回内置默认值。如存在则返回参数默认值
	 * @return mixed|null
	 */
	public function getDefaultValue( string $fieldName, mixed $default = null ) {
		// 闭包执行后的结果值将保存
		if( $this->fieldStructDatas[ $fieldName ]['default'] instanceof \Closure ){
			$this->fieldStructDatas[ $fieldName ]['default'] = $this->extractValue( $this->fieldStructDatas[ $fieldName ]['default'] );
		}
		return $this->fieldStructDatas[ $fieldName ]['default'] ?? $default;
	}

	/**
	 * 提取值(如果为闭包则返回执行后的值)
	 * @param $value
	 * @return mixed
	 */
	private function extractValue( $value ) {
		if( $value instanceof \Closure ){
			// 执行回调函数默认值
			return $value();
		} else{
			return $value;
		}
	}

	/**
	 * 重置指定值(恢复为默认数据)
	 * @param string|null $fieldName [默认][null]重置所有
	 * @return $this
	 */
	public function reset( string $fieldName = null ):self {
		if( $fieldName === null ){
			// 记录最后一次重置的数据
			$this->lastResetFieldStructDatas = $this->fieldStructDatas;
			// 所有
			foreach( $this->fieldStructDatas as $fieldStructKey => $fieldStructValue ){
				$this->fieldStructDatas[ $fieldStructKey ] = $this->buildFieldStructData( $this->getDefaultValue( $fieldStructKey ) );
			}
			//

		} else{
			// 记录最后一次重置的数据
			$this->lastResetFieldStructDatas[ $fieldName ] = $this->fieldStructDatas[ $fieldName ];
			// 指定
			$this->fieldStructDatas[ $fieldName ] = $this->buildFieldStructData( $this->getDefaultValue( $fieldName ) );
		}
		//
		return $this;
	}

	/**
	 * 获取所有已设置的数据
	 * @param bool $isGetDefaultValue 是否缓存默认值
	 * @param bool $keyNameType [false][默认]将Java风格转换为C的风格 [true]将C风格转换为Java的风格
	 * @return array
	 */
	/*	public function getChangeDatas( bool $isGetDefaultValue = false, bool $keyNameType = true):array {
			$gatherData = [];
			foreach ( $this->getChangeStructDatas( $isGetDefaultValue, $keyNameType ) as $fieldStructValue ){
				$gatherData[] = $fieldStructValue['data'];
			}
			//
			return $gatherData;
		}*/

	/**
	 * 获取所有已设置的结构数据(包含type)
	 * @param bool $isGetDefaultValue 是否缓存默认值
	 * @param bool $keyNameType [false]将Java风格转换为C的风格 [true][默认]将C风格转换为Java的风格
	 * @return array
	 */
	public function getChangeStructDatas( bool $isGetDefaultValue = false, bool $keyNameType = true ):array {
		$gatherStructData = [];
		//
		foreach( $this->fieldStructDatas as $fieldKey => $fieldStructValue ){
			if( $isGetDefaultValue === true || $this->issetNewValue( $fieldKey ) ){
				$fieldKeyOfType = $keyNameType ? $fieldKey : com\Lib\Fun\Str::parseName( $fieldKey );
				$value = $this->getValue( $fieldKey );
				if( $value !== null ){
					$gatherStructData[ $fieldKeyOfType ] = $fieldStructValue;
				}
			}
		}
		//
		return $gatherStructData;
	}

}